﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace BigPixels
{
    public partial class Canvas : UserControl
    {
        public event EventHandler CanvasChanged;

        public Canvas()
        {
            InitializeComponent();

            this.DoubleBuffered = true;
            this.Grid = new PixelGrid(32, 32, this.ClientRectangle); // 128, 128;
            this.PaintColour = Color.Black;
            this.CanvasColour = Color.White;
        }

        private void Canvas_Paint(object sender, PaintEventArgs e)
        {
            var graphics = e.Graphics;

            this.Grid.UpdateBounds(this.ClientRectangle);

            DrawPixels(graphics);
            DrawGrid(graphics);
        }

        private void Canvas_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left || e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                this.IsMouseDown = true;
            }
            else if (e.Button == System.Windows.Forms.MouseButtons.Middle)
            {
                // flood fill with this colour...
                try
                {
                    var cell = this.Grid.At(e.Location, this.ClientRectangle);
                    FloodFill(cell.Position.X, cell.Position.Y, cell.Pixel.Value, this.CanvasColour, 0);
                    var saved = CanvasChanged;

                    if (saved != null)
                        saved(this, EventArgs.Empty);

                    this.Invalidate();
                }
                catch (ArgumentOutOfRangeException)
                {
                    // swallow this during drawing...
                }

            }
            
            if (this.IsMouseDown)
            {
                try
                {
                    var cell = this.Grid.At(e.Location, this.ClientRectangle);

                    Color toApply = (e.Button == System.Windows.Forms.MouseButtons.Left) ? this.PaintColour : this.CanvasColour;

                    if (cell.Pixel.Value != toApply)
                    {
                        cell.Pixel.Value = toApply;
                        var saved = CanvasChanged;

                        if (saved != null)
                            saved(this, EventArgs.Empty);

                        this.Invalidate();
                    }
                }
                catch (ArgumentOutOfRangeException)
                {
                    // swallow this during drawing...
                }
            }
        }

        private void FloodFill(int x, int y, Color TargetColour, Color ReplacementColour, int depth)
        {
            if (x < 0 || x >= this.Grid.GridSize.Width)
                return;

            if (y < 0 || y >= this.Grid.GridSize.Height)
                return;

            if (this.Grid[x, y].Pixel.Value != TargetColour)
            {
                return;
            }
            
            if (++depth > 50)
                return;
            
            this.Grid[x, y].Pixel.Value = ReplacementColour;

            FloodFill(x + 1, y, TargetColour, ReplacementColour, depth);
            FloodFill(x - 1, y, TargetColour, ReplacementColour, depth);
            FloodFill(x, y + 1, TargetColour, ReplacementColour, depth);
            FloodFill(x, y - 1, TargetColour, ReplacementColour, depth);
        }
                
        public void SaveToFile(string fileName, ApplicationSettings settings, bool saveWithGrid)
        {
            var rectangle = new Rectangle(0, 0, settings.ExportPixelSize, settings.ExportPixelSize);
            using (Bitmap image = new Bitmap(rectangle.Width, rectangle.Height)) //this.ClientRectangle.Width, this.ClientRectangle.Height))
            {
                using (Graphics graphics = Graphics.FromImage(image))
                {
                    this.Grid.UpdateBounds(rectangle);
                    DrawBackground(graphics);
                    DrawPixels(graphics);

                    if (saveWithGrid)
                        DrawGrid(graphics);
                }

                var format = ImageFormat.Jpeg;

                if (fileName.EndsWith("jpg", StringComparison.CurrentCultureIgnoreCase) 
                    || fileName.EndsWith("jpeg", StringComparison.CurrentCultureIgnoreCase))
                {
                    format = ImageFormat.Jpeg;
                }
                else if (fileName.EndsWith("png", StringComparison.CurrentCultureIgnoreCase))
                {
                    format = ImageFormat.Png;
                }
                else if (fileName.EndsWith("bmp", StringComparison.CurrentCultureIgnoreCase))
                {
                    format = ImageFormat.Bmp;
                }
                
                image.Save(fileName, format); // Or GetPixel, if you want
            }
        }

        public PixelGrid Grid
        {
            get;
            private set;
        }

        private void DrawBackground(Graphics g)
        {
            using (var brush = new SolidBrush(Color.White))
            {
                g.FillRectangle(brush, this.ClientRectangle);
            }
        }

        private void DrawPixels(Graphics g)
        {
            for (int x = 0; x < this.Grid.GridSize.Width; ++x)
            {
                for (int y = 0; y < this.Grid.GridSize.Height; ++y)
                {
                    var cell = this.Grid[x, y];

                    if (cell.Pixel.IsValid)
                    {
                        using (var brush = new SolidBrush(cell.Pixel.Value))
                        {
                            g.FillRectangle(brush, cell.Bounds);
                        }
                    }
                }
            }
        }

        private void DrawGrid(Graphics g)
        {
            using (var pen = new Pen(Color.LightGray, 1.0f))
            {
                for (int x = 0; x < this.Grid.GridSize.Width; ++x)
                {
                    for (int y = 0; y < this.Grid.GridSize.Height; ++y)
                    {
                        var cell = this.Grid[x, y];
                        g.DrawRectangle(pen, cell.Bounds);
                    }
                }
            }
        }

        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (this.IsMouseDown)
            {
                try
                {
                    var cell = this.Grid.At(e.Location, this.ClientRectangle);

                    Color toApply = (e.Button == System.Windows.Forms.MouseButtons.Left) ? this.PaintColour : this.CanvasColour;

                    if (cell.Pixel.Value != toApply)
                    {
                        cell.Pixel.Value = toApply;
                        var saved = CanvasChanged;

                        if (saved != null)
                            saved(this, EventArgs.Empty);

                        this.Invalidate();
                    }
                }
                catch (ArgumentOutOfRangeException)
                {
                    // swallow during drawing
                }
            }
        }

        private void Canvas_MouseUp(object sender, MouseEventArgs e)
        {
            this.IsMouseDown = false;
        }

        private bool IsMouseDown
        {
            get;
            set;
        }

        public Color PaintColour
        {
            get;
            set;
        }

        public Color CanvasColour
        {
            get;
            set;
        }

        public int GridSize
        {
            get
            {
                return this.Grid.GridSize.Width;
            }

            set
            {
                if (value > 0 && value != this.Grid.GridSize.Width)
                {
                    this.Grid = new PixelGrid(value, value, this.ClientRectangle); 
                }
            }
        }
     }
}
